﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace RoketPack.IM
{
    public partial class FriendUI : ListBox
    {
        #region DllImport for Windows XP fix
        [DllImport("User32.dll", PreserveSig = false)]
        private static extern int SystemParametersInfo(uint uiAction, int uiParam, bool pvParam, int fWinIni);
        public static readonly uint SPI_SETLISTBOXSMOOTHSCROLLING = 0x1007;
        #endregion

        private TextBox c_EditorBox = null;
        private ContactTreeNode c_EditedContact = null;

        internal event ContactEventHandler ContactChatStarted;
        internal event ContactEventHandler ContactNicknameChanged;
        protected virtual void OnContactChatStarted(Contact contact)
        {
            this.ContactChatStarted(this, new ContactEventArgs(contact, ContactEventArgs.ActionType.CHAT));
        }
        protected virtual void OnContactNicknameChanged(Contact contact, string nickname)
        {
            this.ContactNicknameChanged(this, new ContactEventArgs(contact, ContactEventArgs.ActionType.NICKNAME, nickname));
        }

        /// <summary>
        /// Creates a new FriendUI component.
        /// </summary>
        public FriendUI()
        {
            InitializeComponent();
            this.DrawMode = DrawMode.OwnerDrawVariable;
            this.ItemHeight = 36;
            this.ScrollAlwaysVisible = true;

            // Fix scrolling issue on Windows XP.
            SystemParametersInfo(SPI_SETLISTBOXSMOOTHSCROLLING, 0, false, 0);
        }

        /// <summary>
        /// This event is raised when we need to render an item within the
        /// FriendUI component.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The draw event information.</param>
        private void FriendUI_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index == -1)
                return;
            if (e.Index >= this.Items.Count)
                return;

            if (this.Items[e.Index] is ContactTreeNode)
            {
                Color back = SystemColors.Window;
                Color fore = Color.Black;
                Color fore2 = Color.DimGray;
                if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
                {
                    back = Color.FromArgb(51, 51, 51);
                    fore = Color.White;
                    fore2 = Color.White;
                }

                Contact contact = ((ContactTreeNode)this.Items[e.Index]).Contact;
                contact.AssociateListItem(this, (ContactTreeNode)this.Items[e.Index]);
                e.Graphics.FillRectangle(new SolidBrush(back), e.Bounds);
                e.Graphics.DrawImage(contact.StateImage, new Rectangle(e.Bounds.X + 10, e.Bounds.Y + 9, 16, 16),
                    new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);

                // Check for editing.
                if (this.c_EditorBox != null && this.c_EditedContact == this.Items[e.Index])
                {
                    if (!this.c_EditorBox.Focused)
                        this.c_EditorBox.Focus();
                    return;
                }

                Font top = new Font(this.Font.FontFamily, 11.5f, GraphicsUnit.Pixel);
                Font bottom = new Font(this.Font.FontFamily, 10.0f, GraphicsUnit.Pixel);
                if (contact.Status == null || contact.Status.Trim() == "")
                {
                    TextRenderer.DrawText(e.Graphics, contact.Representation, top, new Point(32, e.Bounds.Y + 10), fore);
                }
                else
                {
                    TextRenderer.DrawText(e.Graphics, contact.Representation, top, new Point(32, e.Bounds.Y + 5), fore);
                    TextRenderer.DrawText(e.Graphics, contact.Status, bottom, new Point(32, e.Bounds.Y + 19), fore2);
                }
            }
            else if (this.Items[e.Index] is GroupTreeNode)
            {
                GroupTreeNode node = ((GroupTreeNode)this.Items[e.Index]);
                string name = node.Name;

                Color back = SystemColors.Window;
                Color fore = Color.Black;
                if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
                {
                    back = Color.FromArgb(51, 51, 51);
                    fore = Color.White;
                }

                e.Graphics.FillRectangle(new SolidBrush(back), e.Bounds);

                if (node.Expanded && node.UsersOnline != 0)
                {
                    e.Graphics.DrawImage(Properties.Resources.IMTreeMinus, new Rectangle(4, e.Bounds.Y + 6, 9, 9),
                        new Rectangle(0, 0, 9, 9), GraphicsUnit.Pixel);
                    e.Graphics.DrawLine(new Pen(new SolidBrush(Color.FromArgb(128, 128, 128))), new Point(0, e.Bounds.Bottom - 1),
                        new Point(this.Width, e.Bounds.Bottom - 1));
                }
                else
                    e.Graphics.DrawImage(Properties.Resources.IMTreePlus, new Rectangle(4, e.Bounds.Y + 6, 9, 9),
                        new Rectangle(0, 0, 9, 9), GraphicsUnit.Pixel);

                Font f = new Font("Verdana", 10.0f, FontStyle.Bold, GraphicsUnit.Pixel);
                Font f2 = new Font(f, FontStyle.Regular);
                Size s = TextRenderer.MeasureText(e.Graphics, name, f);
                TextRenderer.DrawText(e.Graphics, name, f, new Point(15, e.Bounds.Y + 4), fore);
                TextRenderer.DrawText(e.Graphics, "(" + node.UsersOnline + "/" + node.UsersTotal + ")", f2,
                    new Point(15 + s.Width, e.Bounds.Y + 4), fore);
            }
        }

        /// <summary>
        /// This event is raised when the underlying control needs to know the size
        /// of the specified item at the specified index.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The measurement event information.</param>
        private void FriendUI_MeasureItem(object sender, MeasureItemEventArgs e)
        {
            if (e.Index == -1)
                return;
            if (e.Index >= this.Items.Count)
                return;

            if (this.Items[e.Index] is ContactTreeNode)
                e.ItemHeight = 36;
            else if (this.Items[e.Index] is GroupTreeNode)
                e.ItemHeight = 20;
        }

        /// <summary>
        /// This event is raised when the FriendUI control recieves a mouse click.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The mouse event information.</param>
        private void FriendUI_MouseClick(object sender, MouseEventArgs e)
        {
            this.EndEdit();

            // Get the item that was underneath the mouse click.
            object node = null;
            for (int i = 0; i < this.Items.Count; i += 1)
            {
                if (this.GetItemRectangle(i).Contains(e.Location))
                {
                    node = this.Items[i];
                }
            }
            if (node == null)
                return;

            if (node is GroupTreeNode)
            {
                GroupTreeNode gtn = (GroupTreeNode)node;
                if (gtn.UsersOnline != 0)
                    gtn.Expanded = !gtn.Expanded;
            }
        }

        /// <summary>
        /// This event is raised when the FriendUI control recieves a double click
        /// from the mouse.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void FriendUI_DoubleClick(object sender, EventArgs e)
        {
            this.EndEdit();

            if (this.SelectedItem is ContactTreeNode)
            {
                ContactTreeNode ctn = (ContactTreeNode)this.SelectedItem;
                this.OnContactChatStarted(ctn.Contact);
            }
        }

        /// <summary>
        /// Begins editing a contact's nickname in the contact list.
        /// </summary>
        /// <param name="item">The node that represents the contact.</param>
        internal void BeginEdit(ContactTreeNode item)
        {
            // Ensure there are no other edits currently taking place.
            this.EndEdit();

            // Start the editing.
            Rectangle bounds = this.GetItemRectangle(this.Items.IndexOf(item));

            this.c_EditedContact = item;
            this.c_EditorBox = new TextBox();
            this.c_EditorBox.Size = new Size(this.Width - 72, 24);
            this.c_EditorBox.Location = new Point(bounds.X + 32, bounds.Y + 7);
            this.c_EditorBox.Visible = true;
            this.c_EditorBox.Text = item.Contact.Representation;
            this.c_EditorBox.SelectAll();
            EventHandler resize = new EventHandler((sender, e) =>
                {
                    this.c_EditorBox.Width = this.Width - 72;
                });
            this.c_EditorBox.KeyDown += new KeyEventHandler((sender, e) =>
                {
                    if (e.KeyCode == Keys.Enter)
                    {
                        // Finish editing.
                        this.Resize -= resize;
                        this.EndEdit();
                    }
                });
            this.Resize += resize;
            this.Controls.Add(this.c_EditorBox);
            this.c_EditorBox.Focus();
        }

        /// <summary>
        /// Ends the editing of a contact's nickname in the contact list.
        /// </summary>
        internal void EndEdit()
        {
            if (this.c_EditorBox != null)
            {
                this.OnContactNicknameChanged(this.c_EditedContact.Contact, this.c_EditorBox.Text);
                this.Controls.Remove(this.c_EditorBox);
                this.c_EditorBox.Dispose();
                this.c_EditorBox = null;
            }
        }
    }

    /// <summary>
    /// An object which represents a contact in the FriendUI list.
    /// </summary>
    internal class ContactTreeNode
    {
        private Contact p_Contact = null;

        /// <summary>
        /// Creates a new ContactTreeNode that represents the specified contact.
        /// </summary>
        /// <param name="contact">The contact to represent.</param>
        internal ContactTreeNode(Contact contact)
        {
            this.p_Contact = contact;
        }

        /// <summary>
        /// The contact that this object represents.
        /// </summary>
        internal Contact Contact
        {
            get
            {
                return this.p_Contact;
            }
            set
            {
                this.p_Contact = value;
            }
        }
    }

    /// <summary>
    /// An object which represents a group of contacts under a specified
    /// text name.
    /// </summary>
    internal class GroupTreeNode
    {
        private string p_Name = null;
        private List<ContactTreeNode> p_Children = new List<ContactTreeNode>();
        private bool p_Expanded = true;
        private FriendUI m_FriendUI = null;

        /// <summary>
        /// Creates a new GroupTreeNode with the specified group name.
        /// </summary>
        /// <param name="name">The group name.</param>
        internal GroupTreeNode(string name)
        {
            this.p_Name = name;
        }

        /// <summary>
        /// The name of the group.
        /// </summary>
        internal string Name
        {
            get
            {
                return this.p_Name;
            }
            set
            {
                this.p_Name = value;
            }
        }

        /// <summary>
        /// The contacts which are listed in this group.
        /// </summary>
        internal List<ContactTreeNode> Children
        {
            get
            {
                return this.p_Children;
            }
            set
            {
                this.p_Children = value;
            }
        }

        /// <summary>
        /// Whether this group is currently expanded in the FriendUI list.
        /// </summary>
        internal bool Expanded
        {
            get
            {
                return this.p_Expanded;
            }
            set
            {
                if (this.p_Expanded == value)
                    return;

                this.p_Expanded = value;

                if (!this.p_Expanded)
                {
                    foreach (ContactTreeNode ctn in this.p_Children)
                    {
                        if (this.m_FriendUI.Items.Contains(ctn))
                            this.m_FriendUI.Items.Remove(ctn);
                    }
                }
                else
                {
                    int i = 1;
                    foreach (ContactTreeNode ctn in this.p_Children)
                    {
                        if (!ctn.Contact.Online)
                            continue;

                        this.m_FriendUI.Items.Insert(this.m_FriendUI.Items.IndexOf(this) + i, ctn);
                        i += 1;
                    }
                }
            }
        }

        /// <summary>
        /// The total number of contacts in this group.
        /// </summary>
        internal int UsersTotal
        {
            get
            {
                return this.p_Children.Count;
            }
        }

        /// <summary>
        /// The total number of online contacts in this group.
        /// </summary>
        internal int UsersOnline
        {
            get
            {
                int ret = 0;
                foreach (ContactTreeNode node in this.p_Children)
                {
                    if (node.Contact.Online)
                        ret += 1;
                }
                return ret;
            }
        }

        /// <summary>
        /// Associates this group object with a FriendUI control.
        /// </summary>
        /// <param name="ui">The FriendUI control.</param>
        internal void AssociateList(FriendUI ui)
        {
            this.m_FriendUI = ui;
        }
    }
}
